Repository navigation
feat: project named derivatives onto an explicit order - #89
Merged
Merged
Conversation
Named derivatives were a dead end for anything that wants arrays. There was no way from an SScalar to a gradient vector or a Hessian matrix, and hj.d/hj.dd made it worse: they check for .g and .hm, a named scalar has neither, so they silently returned an empty array. A wrong answer rather than a missing feature. g(names) and hm(names) project onto an explicit, ordered list of names. The order comes from the caller, which is the point: values carrying different names project onto one shared layout without any of them being padded or remapped first. That is what an assembly step needs, and it is where named variables earn their keep over indexed ones -- no index bookkeeping between the local computation and the global system. A name the value does not carry reads as zero, the same rule d() and dd() already follow. hj.d and hj.dd take the same list via names=, and without it they now raise TypeError for named scalars instead of returning empty arrays. The indexed path is unchanged. variables(dict) builds a set in one call, in the order the dict gives, which is the order to hand back to g() and hm(). Lookups are batched: one binary search per requested name rather than one per matrix entry, so a k-by-k Hessian costs k log n + k^2 instead of k^2 log n. The mutation check on the new tests found one gap worth recording. The guard that skips unknown names in hm() can be removed without any assertion failing -- at(k, -1, j) reads before the buffer, and the garbage happened to be the zero the test expects. UBSan aborts on it, so the sanitizer job is what pins that guard, not the value comparisons.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Builds on #88. Named derivatives were a dead end for anything that wants arrays.
There was no way from an
SScalarto a gradient vector or a Hessian matrix — and the module-level helpers made it worse.hj.dandhj.ddcheck for.gand.hm; a named scalar has neither, so they returned an empty array:A wrong answer rather than a missing feature.
Projection
g(names)andhm(names)project onto an explicit, ordered list of names:That the order comes from the caller is the point. Values carrying different names project onto one shared layout without any of them being padded or remapped first:
That is what an assembly step needs, and it is where named variables earn their keep over indexed ones — no index bookkeeping between the local computation and the global system.
Without
names,hj.d/hj.ddnow raiseTypeErrorfor named scalars. The indexed path is unchanged.variables(dict)builds a set in one call, in the order the dict gives — the order to hand back tog()/hm().Lookups are batched: one binary search per requested name rather than one per matrix entry, so a k-by-k Hessian costs
k log n + k²instead ofk² log n.One gap the mutation check found
Worth recording rather than hiding. The guard that skips unknown names in
hm()can be deleted and every assertion still passes —at(k, -1, j)reads before the buffer, and the garbage happened to be exactly the zero the test expects. UBSan aborts on it:So the sanitizer job is what pins that guard, not the value comparisons. The other five mutations behaved as expected: ignoring the positions failed 2 assertions, a wrong zero-fill 3, doubled values 11, and the two symmetry-equivalent index swaps correctly did not fail — for a symmetric matrix both mappings are right.
Verification
-Werrorjust tidyno new findings